package com.hdx;

import org.apache.log4j.Logger;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;

import java.util.List;
import java.util.concurrent.CountDownLatch;

/**
 * @Auther: hdx
 * @Date: 2019/1/29 14:55
 * @Description: 队列式的 （懒汉式的。当进程的锁被释放之后通知ZK，然后ZK在通知等待的进程）
 * 注意：zk不能把所有的等待的进程都通知，所以把等待的进程放到队列里面，只通知一个人
 */
public class DistributedLockQuee {

    private static Logger logger = Logger.getLogger(TestZookeeper.class);
    private static String connectString = "10.0.0.114:2181,10.0.0.115:2181,10.0.0.116:2181";
    private static int sessionTimeout = 2000;//毫秒
    private static ZooKeeper zooKeeper;
    private static final String PARENTPATH = "/dLocks";//存放锁的父节点

    CountDownLatch cd = new CountDownLatch(1);
    CountDownLatch cd2 = new CountDownLatch(1); //主要卡住当前进程，直到分布式锁已经释放（也就是上一个子节点删除了）

    /**
     * 初始化zookeeper链接对象
     */
    public DistributedLockQuee() throws Exception {
        {
            //多长时间链接不上zk了就会session会话超时
            /**
             * 建立和zookeeper集群的连接，操作都是用它
             */
            zooKeeper = new ZooKeeper(connectString, sessionTimeout, new Watcher() {
                //这个是wathcher的回调方法：跟ajax的回调是一个意思，连上后会回调到这个方法里
                public void process(WatchedEvent watchedEvent) {
                    System.out.println("事件回调：" + watchedEvent.getState());
                    if (watchedEvent.getState() == Event.KeeperState.SyncConnected) { //如果异步连接成功了
                        cd.countDown();
                    }
                }
            });
            cd.await(); //给他手动卡住，不让事件这个方法结束，等待事件回调
            //   System.out.println("await了");
        }
    }

    /**
     * 申请锁（懒汉式的 ）
     */
    public String  getLock(String ITem_id) throws Exception {
        //1、创建父节点检查是否存在
        Stat exists = zooKeeper.exists(PARENTPATH, false);
        if (exists == null) {
            zooKeeper.create(PARENTPATH, "this is a distributed parent_path".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);  //用持久化的节点
        }
        //1.1看id父节点是否存在
        String id_path = PARENTPATH + "/" + ITem_id; //存放id的父节点
        Stat exists_item = zooKeeper.exists(id_path, false);
        if (exists_item == null) {
            zooKeeper.create(id_path, "this is a distributed parent_path".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);  //用持久化的节点
        }
        //2、创建带有唯一标识的子节点(创建带序号的)
        String lock_Path = id_path + "/lock_";
        //临时自动编号的子节点，这个最终的锁节点一定会创建成功
        String create_currentPath = zooKeeper.create(lock_Path, "zijiedian".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
        //检查我是不是锁的持有者：
            //①是否在当前节点还有兄弟节点，如果没有则自己这个进程就是锁的持有者
            //②如果有其他兄弟节点，则看一下跟自己相比的上一个节点是否还存在，如果不存在自己就可以获取到了，如果存在则说明还没排到自己，则监听（等待事件监听）
        System.out.println("create_currentPath:"+create_currentPath);
        List<String> childrens = zooKeeper.getChildren(id_path, false);
        if(childrens.size()==1){
            System.out.println("只有自己一个进程在等待，可以获取锁");
            return create_currentPath; //返回当前创建的节点的路径，等释放锁的时候用
        }
        //开启监听:监听/dLocks/1024/lock_0000000007 -1 啥时候消失，消失后才能获取到锁
        String lock_ = create_currentPath.substring(create_currentPath.indexOf("lock_")+5);
        int preIndex= Integer.valueOf(lock_)-1; //TODO 这里还有一个bug，比如1、2、3个子节点 突然2 挂了，此时1还在执行中，这里的3来通过减1的方式来判断上一个子节点是否移除就有问题了
          //TODO 上面的问题可以通过并发容器解决
        //补位
        //获取上一个节点的路径
        String prePath=id_path+"/lock_"+String.format("%010d",preIndex);
       // System.out.println(prePath);
        zooKeeper.exists(prePath, new Watcher() {
            public void process(WatchedEvent watchedEvent) {
                if(watchedEvent.getType()== Event.EventType.NodeDeleted){ //如果监听到上一个子节点已经被删除了
                    System.out.println("上一个节点已经释放：当前进程获得控制权");
                    cd2.countDown();
                }
            }
        });
        cd2.await();//卡住当前线程
        return create_currentPath;
    }

    /**
     * 释放锁
     */
    public void releaseLock(String lockPath) throws Exception {
        System.out.println("释放锁了");
        zooKeeper.delete(PARENTPATH + "/" +lockPath, -1);
    }
}
